package session;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.PriorityQueue;

import appserver.Constants;

@SuppressWarnings("serial")
public class Session implements Serializable, Comparable<Session>{
	
	/*These variables compose the state of a session, which is kept under 1k as per
	 * the assignment requirements - sessionId will be encoded as a ASCII char[] of 9 bytes,
	 * expirationTime is represented by 8 bytes, sessionVersion 4 bytes, message length 2 bytes, and a message of 
	 * maximum length of 1000 bytes (char[] array)
	 * SEE METHODS packSessionState and unpackSessionState for more info
	 */
	SessionID sessionID;
	int sessionVersion;
	String message;
	long discard_time; //time to expire, in millis
	
	public Session(SessionID sessionID, int expiresInSeconds, String message) {
		this(sessionID, (long) (expiresInSeconds * 1000) + System.currentTimeMillis(), message, 0);
	}
	
	/**
	 * @param sessionID
	 * @param expirationTime
	 * @param message
	 * @param sessionVersion
	 */
	public Session(SessionID sessionID, long discard_time, String message, int sessionVersion) {
		if (message.length() > 500) {
			message = message.substring(0, 500); //ensures session is smaller than 1kb
		}
		this.sessionID = sessionID;
		this.sessionVersion = sessionVersion;
		this.message = message;
		this.discard_time = discard_time;
	}
	
	public int updateVersion() {
		sessionVersion += 1;
		return sessionVersion;
	}

	/**
	 * @return the sessionID
	 */
	public SessionID getSessionID() {
		return sessionID;
	}

	/**
	 * @param sessionID the sessionID to set
	 */
	public void setSessionID(SessionID sessionID) {
		this.sessionID = sessionID;
	}

	/**
	 * @return the sessionVersion
	 */
	public int getSessionVersion() {
		return sessionVersion;
	}

	/**
	 * @param sessionVersion the sessionVersion to set
	 */
	public void setSessionVersion(int sessionVersion) {
		this.sessionVersion = sessionVersion;
	}

	/**
	 * @return the message
	 */
	public String getMessage() {
		return message;
	}

	/**
	 * @param message the message to set
	 */
	public void setMessage(String message) {
		this.message = message;
	}

	/**
	 * @return the expirationTime
	 */
	public long getDiscard_time() {
		return discard_time;
	}

	/**
	 * @param expirationTime The time it will expire, in milliseconds
	 */
	
	public void setDiscard_time(int secondsFromNow) {
		long millis = (long) secondsFromNow * 1000;
		this.discard_time = System.currentTimeMillis() + millis;
	}
	
	public void setDiscard_time(long expirationTime) {
		this.discard_time = expirationTime;
	}
	
	/**
	 * @return
	 * Determine if session has expired based on current system time
	 */
	public boolean isExpired() {
		return (System.currentTimeMillis() > discard_time);
	}
	
	public boolean needsUpdate(){
		return((System.currentTimeMillis()/1000 + Constants.SESSION_TIMEOUT_SECS) > this.getDiscard_time());
	}
	
	/**
	 * Returns a byte[] representation of the current session state, less than 1024 bytes
	 * @param session
	 * @return
	 * @throws IOException
	 */
	public static byte[] packSessionState(Session session) throws IOException {
	    ByteArrayOutputStream bos = new ByteArrayOutputStream();  
	    DataOutputStream dos = new DataOutputStream(bos);
	    //trim message size
	    String message = session.getMessage();
	    if (message.length() >= 1000) {
	    	message = message.substring(0, 1000);
	    }
	    else {
	    	message = message.substring(0, message.length());
	    }
	    short messageLength = (short) message.length();
	    //trim sessionId (should not be necessary, but do it anyway)
	    String sessionId = session.getSessionID().toString();
	    sessionId = sessionId.substring(0,9);
	    
	    dos.writeLong(session.getDiscard_time());  
	    dos.writeInt(session.getSessionVersion());
	    dos.writeShort(messageLength);
	    dos.writeChars(sessionId);
	    dos.writeChars(message);
	    // etc.  
	    dos.flush();  
	    byte[] data = bos.toByteArray();  
		return data;
	}
	/**
	 * Generates a session object based on a byte representation
	 * @param sessionState
	 * @return
	 * @throws IOException
	 */
	public static Session unpackSessionState(byte[] sessionState) throws IOException {
		ByteArrayInputStream bis = new ByteArrayInputStream(sessionState);
		DataInputStream dos = new DataInputStream(bis);	
		long expirationTime = dos.readLong();
		int sessVersion = dos.readInt();
		short mLength = dos.readShort();
		
		//read sessId
		StringBuffer sessIdBuf = new StringBuffer();
		int j = 0;
		while (j++ < 9) {
			sessIdBuf.append(dos.readChar());
		}
		String sessId = sessIdBuf.toString();
		
		//read message
		StringBuffer msgBuf = new StringBuffer();
		for (int i = 0; i < mLength; i++) {
			char next = dos.readChar();
			msgBuf.append(next);
		}
		String message = msgBuf.toString();
		
		//build session
		SessionID sessionId = new SessionID(sessId);
		Session session = new Session(sessionId, expirationTime, message, sessVersion);
		return session;
		
	}

	@Override
	public int compareTo(Session arg0) {
		if (this.discard_time< arg0.discard_time)
			return -1;
		if (this.discard_time== arg0.discard_time)
			return 0;
		else
			return 1;
	}
	
	public static void main(String[] args) {
		//SessionID sessionID, long discard_time, String message, int sessionVersion
		
		Session sess1= new Session(new SessionID("1",1), 4, "one", 2);
		Session sess2= new Session(new SessionID("1",2), 5, "two", 2);
		Session sess3= new Session(new SessionID("1",3), 6, "three", 2);
		Session sess4= new Session(new SessionID("1",4), 7, "four", 2);
		
		PriorityQueue<Session> order= new PriorityQueue<Session>();
		
		order.add(sess3);
		order.add(sess4);
		order.add(sess1);
		order.add(sess2);
		
		int count= 4;
		System.out.println("now try");
		while(count>0)
		{
			Session pulled= order.poll();
			System.out.println(pulled.getMessage());
			count--;
		}
		
	}
}
